<?php

	define('EVERYONE_GROUP_ID', 1);
	define('LOGGED_IN_GROUP_ID', 2);
	define('VERSION', 0.1);
	ini_set('display_errors', 1);
	
	function getAjaxLink($method){
		return URL . 'ajax/' . $method;
	}
	
	function dump($var){
		echo '<pre>';
		echo var_dump($var);
		echo '</pre>';
	}
	
	function convertDataSize($size){
		 $unit = array('b', 'kb', 'mb', 'gb', 'tb', 'pb');
		 return @round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . ' ' . $unit[$i];
	}
	
	function css($file){
		return '<link href="' . URL . 'public' . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . $file . '" rel="stylesheet" type="text/css" />' . "\n";
	}
	
	function js($file){
		return '<script type="text/javascript" src="' . URL . 'public' . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . $file . '"></script>' . "\n"; 
	}
	
	function __autoload($className){
		$file = APP_DIR . 'models' . DIRECTORY_SEPARATOR . $className . '.php';
		
		if(file_exists($file)){
			include($file);
		}
	}
	
	function lang($key){
		return Application::get_lang_item($key);
	}
	
	function alert($message){
		echo '<script type="text/javascript">alert(\'' . $message . '\');</script>';
	}
	
	class Benchmark{
		public static $markers = array();
	
		public function mark($name){
			self::$markers[$name] = microtime();
		}	
		
		public function elapsed($marker1, $marker2 = null, $decimals = 4){
			if($marker2 === null){
				$marker2 = microtime();
			} else {
				if(!isset(self::$markers[$marker2])){
					exit('Could not return benchmark from ' . $marker2 . ' because it was not defined');
				}
				
				$marker2 = $markers[$marker2];
			}
			
			if(!isset(self::$markers[$marker1])){
				exit('Could not return benchmark from ' . $marker1 . ' because it was not defined');
			}
			
			$marker1 = $markers[$marker1];
			
			list($sm, $ss) = explode(' ', $marker1);
			list($em, $es) = explode(' ', $marker2);
			
			return number_format(($em + $es) - ($sm + $ss), $decimals);		
		}
		
		public function getExecutionTime(){
			list($sm, $ss) = explode(' ', START);
			list($em, $es) = explode(' ', microtime());
			
			return number_format(($em + $es) - ($sm + $ss), 4);	
		}
		
		public function getMemoryUsage(){
			return convertDataSize(memory_get_usage(true));
		}
	}
	
	class Mysql_Database extends Pdo{
		private $queryCount;
		
		public function __construct($host, $port, $name, $username, $password){
			parent::__construct('mysql:host=' . $host . ';port=' . $port . ';dbname=' . $name, $username, $password);
		}
		
		public function query($query, $parameters = null){
			$stmt = $this->prepare($query);
			$stmt->setFetchMode(PDO::FETCH_ASSOC);
			
			if($parameters !== null){
				if(!is_array($parameters)){
					$parameters = array($parameters);
				}
				
				$stmt->execute($parameters);
			} else {
				$stmt->execute();
			}
			
			$error = $stmt->errorInfo();
			
			if(isset($error[2]) && !empty($error[2])){
				exit('Query: <b>' . $query . '</b> has failed: <b>' . $error[2] . '</b>');
			}
			
			return array('query' => $query, 'parameters' => $parameters, 'result' => $stmt->fetchAll(), 'rowCount' => $stmt->rowCount());
		}
	}
	
	class Session{
		private $id;
		private $data;
		private $db;
		private $cookieName;
		private $hash;
		private $lifetime;
		private $domain;
		private $tableName;
		private $isLoggedIn;
		private $accessKeys;
		
		public function __construct($hash, $cookieName, $lifetime, $domain, $tableName){
			$this->db =& Application::database('default');
			$this->hash = $hash;
			$this->cookieName = $cookieName;
			$this->lifetime = $lifetime;
			$this->domain = $domain;
			$this->tableName = $tableName;
			$this->isLoggedIn = false;
			$this->data = array();
			$this->accessKeys = array();
		}
		
		public function __get($key){
			if(isset($this->data[$key])){
				return $this->data[$key];
			} else {
				return null;
			}
		}
		
		public function __set($key, $value){
			$this->data[$key] = $value;
		}
		
		public function start(){
			if(!isset($_COOKIE[$this->cookieName])){
				$id = md5($this->hash . microtime(true));
				setcookie($this->cookieName, $id, time() + 3600, '/');
				$_COOKIE[$this->cookieName] = $id;
			}
			
			$this->id = $_COOKIE[$this->cookieName];
			
			$stmt = $this->db->query('SELECT `id`, `data`, `accessKeys` FROM `' . $this->tableName . '` WHERE `id` = ? AND `ip` = ?', array($this->id, $_SERVER['REMOTE_ADDR']));
			
			if($stmt['rowCount'] > 0){
				$this->data = unserialize($stmt['result'][0]['data']);
				$this->accessKeys = unserialize($stmt['result'][0]['accessKeys']);
				$stmt = $this->db->query('UPDATE `' . $this->tableName . '` SET `lastUpdated` = NOW() WHERE `id` = ?', array($this->id));
			} else {
				$this->accessKeys = array();
				$this->data = array();
				$stmt = $this->db->query('INSERT INTO `' . $this->tableName . '`(`id`, `data`, `ip`) VALUES (?, ?, ?)', array($this->id, serialize(array()), $_SERVER['REMOTE_ADDR']));
			}
			
			$this->isLoggedIn = $this->isLoggedInCheck();
		}
		
		public function setAccessKey($functionName, $bool = true){
			$this->accessKeys[$functionName] = $bool;
		}
		
		public function getAccessKey($functionName){
			if($this->accessKeys[$functionName] !== null && $this->accessKeys[$functionName] === true){
				return true;
			}
			
			return false;
		}
		
		private function isLoggedInCheck(){
			if(isset($this->data['userId'])){
				return true;
			}
			
			return false;
		}
		
		public function stop(){
			$this->id = null;
			$_COOKIE[$this->cookieName] = null;
			$this->isLoggedIn = false;
			$this->data = array();
			$this->accessKeys = array();
			setcookie($this->cookieName, '', time() - 3600, '', $this->domain, false, true);
		}
		
		public function isLoggedIn(){
			return $this->isLoggedIn;
		}
		
		public function update(){
			$this->db->query('UPDATE `' . $this->tableName . '` SET `data` = ?, `accessKeys` = ?, `lastUpdated` = NOW()', array(serialize($this->data), serialize($this->accessKeys)));
			$this->db->query('DELETE FROM `' . $this->tableName . '` WHERE `lastUpdated` > (NOW() + INTERVAL ' . $this->lifetime . ' MINUTE)');
		}
		
		public function getData(){
			return $this->data;
		}
	}
	
	class Application{
		private $routing;
		private $controller;
		private $session;
		
		private static $instances;
		private static $connections;
		private static $config;
		private static $lang;
		
		public static function &instance($key, $value = null){
			if($value !== null){
				self::$instances[$key] = $value;
			}
			
			if(isset(self::$instances[$key])){
				return self::$instances[$key];
			} else {
				return null;
			}
		}
		
		public static function &database($name){
			if(!isset(self::$connections[$name])){
				if(isset(self::$config['database']['connections'][$name])){
					$settings = self::$config['database']['connections'][$name];
					
					if($settings['provider'] === 'mysql'){
						self::$connections[$name] = new Mysql_Database($settings['host'], $settings['port'], 
						$settings['name'], $settings['username'], $settings['password']);
					}
				} else {
					exit('Could not get database settings <b>' . $name . '</b>');
				}
			}
			
			return self::$connections[$name];
		}	
	
		public function __construct(&$config){
			self::$config = $config;
			self::$instances = array();
			self::$connections = array();
		}
		
		public function start(){
			$this->loadSession();
			
			if($this->clientHasAccess()){
			
				$this->loadLanguage();
				$this->parseAndTranslateUrl();
				$this->loadController();
				$this->insertControllerObjects();
				$this->loadExternalLibraries();
				$this->loadControllerAction();
				$this->postProcessing();
			} else {
				$this->show404('noPageAccessTitle', 'noPageAccessMessage');
			}
		}
		
		private function loadSession(){
			$settings =& self::$config['session'];
			$this->session = new Session($settings['hash'], $settings['cookieName'], 
			$settings['lifeTime'], self::$config['application']['domain'], 
			self::$config['database']['frameworkTableNames']['sessions']);
			$this->session->start();
		}
		
		public static function get_lang_item($key){
			if(isset(self::$lang[$key])){
				return self::$lang[$key];
			}
			
			return '[Language error: ' . $key . ']';
		}
		
		private function loadLanguage(){
			if($this->session->languagePrefix === null){
				$this->session->languagePrefix = self::$config['application']['defaultLanguage'];
			}
			
			$file = APP_DIR . 'languages' . DIRECTORY_SEPARATOR . $this->session->languagePrefix . '.php';
			
			if(file_exists($file)){
				include($file);
				
				if(isset($lang)){
					self::$lang =& $lang;
				} else {
					exit('Language file: ' . $file . ' got included, but it did not have an array "lang"');				
				}
			} else {
				exit('Language file: ' . $file . ' not found');
			}
		}		
		
		private function parseAndTranslateUrl(){
			$this->routing = array(
				'controller' => self::$config['routing']['defaultController'],
				'action' => self::$config['routing']['defaultAction'],
				'parameters' => array()
			);
			
			if(isset($_SERVER['PATH_INFO'])){
				$url = array_filter(explode('/', $_SERVER['PATH_INFO']));

				if(isset($url[1]) && !empty($url[1])){
					if(count(self::$config['routing']['translations']) > 0){
						foreach(self::$config['routing']['translations'] as $from => $to){
							foreach($url as &$urlSegment){
								if($urlSegment === $from){
									$urlSegment = $to;
								}
							}
						}
					}
				
					$this->routing['controller'] = $url[1];
					array_shift($url);
					
					if(isset($url[0]) && !empty($url[0])){
						$this->routing['action'] = $url[0];
						array_shift($url);
						$this->routing['parameters'] = $url;
					}
				}
			}
		}
		
		private function loadController(){
			$file = APP_DIR . 'controllers' . DIRECTORY_SEPARATOR . $this->routing['controller'] . '.php';
			
			if(file_exists($file)){
				include($file);
				
				if(class_exists($this->routing['controller'])){
					$this->controller = new $this->routing['controller'];
					
					if(method_exists($this->controller, 'load')){
						call_user_func(array($this->controller, 'load'));
					}
				} else {
					exit('Controller file: ' . $file . ' got included but the class: ' . $this->routing['controller'] . ' was not found');
				}
			} else {
				$this->show404('pageNotFoundTitle', 'pageNotFoundMessage');
			}
		}
		
		private function insertControllerObjects(){
			$this->controller->session =& $this->session;
		}
		
		private function clientHasAccess(){
			$db =& self::database('default');
			
			$defaultTables = self::$config['database']['frameworkTableNames'];
			
			// Everyone access.
			$stmt = $db->query('SELECT `accessString` FROM `' . $defaultTables['userGroupPermissions'] . '` WHERE `id` = ' . EVERYONE_GROUP_ID);
			
			if($stmt['rowCount'] > 0){
				foreach($stmt['result'] as $row){
					if($this->isAccessStringGivingAccess($row['accessString'])){
						return true;
					}
				}
			}
			
			if($this->session->isLoggedIn()){
				$stmt = $db->query('SELECT `isDeveloper` FROM `' . $defaultTables['users'] . '` WHERE `id` = ?', array($this->session->userId));
				
				if($stmt['rowCount'] > 0){
					if($stmt['result'][0]['isDeveloper'] === '1'){
						return true;
					}
				}
				
				$stmt = $db->query('SELECT `accessString` FROM `' . $defaultTables['userGroupPermissions'] . '` WHERE `id` = ' . LOGGED_IN_GROUP_ID);
				
				if($stmt['rowCount'] > 0){
					foreach($stmt['result'] as $row){
						if($this->isAccessStringGivingAccess($row['accessString'])){
							return true;
						}
					}
				}
				
				// get the group(s) id of the user now.
				$stmt = $db->query('SELECT `groupId` FROM `' . $defaultTables['userGroupLink'] . '` WHERE `userId` = ?', array($this->session->userId));
				
				if($stmt['rowCount'] > 0){
					foreach($stmt['result'] as $group){
						$groupPermissions = $db->query('SELECT `accessString` FROM `' . $defaultTables['userGroupPermissions'] . '` WHERE `id` = ?', array($group['groupId']));
						
						if($groupPermissions['rowCount'] > 0){
							foreach($groupPermissions['result'] as $row){
								if($this->isAccessStringGivingAccess($row['accessString'])){
									return true;
								}
							}
						}						
					}
				}
			}
			
			return false;
		}
		
		private function isAccessStringGivingAccess($accessString){
			if($accessString === '*'){
				return true;
			}
			
			$accessStringSegments = explode('/', $accessString);
			
			if($accessStringSegments[0] === $this->routing['controller']){
				if($accessStringSegments[1] === '*' || $accessStringSegments[1] === $this->routing['action']){
					return true;
				}
			}
			
			return false;
		}
		
		private function loadExternalLibraries(){
			$externalLibraries = self::$config['application']['externalLibraries'];
			
			foreach($externalLibraries as $lib){
				$file = FW_DIR . 'external libs' . DIRECTORY_SEPARATOR . $lib;
				
				if(file_exists($file)){
					include($file);
				} else {
					exit('Tried to include external library: ' . $file . ' but the file did not exist');
				}
			}
		}
		
		private function loadControllerAction(){
			if(method_exists($this->controller, $this->routing['action'])){
				if(count($this->routing['parameters']) > 0){
					call_user_func_array(array($this->controller, $this->routing['action']), $this->routing['parameters']);
				} else {
					call_user_func(array($this->controller, $this->routing['action']));
				}
			} else {
				$this->show404('pageNotFoundTitle', 'pageNotFoundMessage');
			}
		}
		
		private function postProcessing(){
			if(method_exists($this->controller, 'unload')){
				call_user_func(array($this->controller, 'unload'));
			}
			
			$this->session->update();
		}
		
		private function show404($message, $title){
			include(APP_DIR . 'views' . DIRECTORY_SEPARATOR . 'errors' . DIRECTORY_SEPARATOR . '404.phtml');
			exit;
		}
	}
	
	class View{
		private $data;
		private $file;
		
		public function setFile($fileName){
			$this->file = APP_DIR . 'views' . DIRECTORY_SEPARATOR . $fileName;
		}
		
		public function display(){
			if(count($this->data) > 0){
				foreach($this->data as $key => $value){
					$$key = $value;
				}
			}
			
			include($this->file);
		}
		
		public function __toString(){
			$this->display();
			return '';
		}
		
		public function __get($key){
			if(isset($this->data[$key])){
				return $this->data[$key];
			} else {
				return null;
			}
		}
		
		public function __set($key, $value){
			$this->data[$key] = $value;
		}		
	}
	
?>